﻿' ******************************************************************************
' ** 
' **  Yahoo! Managed
' **  Written by Marius Häusler 2011
' **  It would be pleasant, if you contact me when you are using this code.
' **  Contact: YahooFinanceManaged@gmail.com
' **  Project Home: http://code.google.com/p/yahoo-finance-managed/
' **  
' ******************************************************************************
' **  
' **  Copyright 2011 Marius Häusler
' **  
' **  Licensed under the Apache License, Version 2.0 (the "License");
' **  you may not use this file except in compliance with the License.
' **  You may obtain a copy of the License at
' **  
' **    http://www.apache.org/licenses/LICENSE-2.0
' **  
' **  Unless required by applicable law or agreed to in writing, software
' **  distributed under the License is distributed on an "AS IS" BASIS,
' **  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
' **  See the License for the specific language governing permissions and
' **  limitations under the License.
' ** 
' ******************************************************************************
Imports System.Text.RegularExpressions


Namespace YahooManaged.Finance.API

    ''' <summary>
    ''' Provides methods for downloading historic quote data.
    ''' </summary>
    ''' <remarks></remarks>
    Public Class HistQuotesDownload
        Inherits Base.Download
        Implements Base.IStringDownload

        ''' <summary>
        ''' Raises if an asynchronous download of historic quote data completes.
        ''' </summary>
        ''' <param name="sender">The event raising object</param>
        ''' <param name="ea">The event args of the asynchronous download</param>
        ''' <remarks></remarks>
        Public Event AsyncDownloadCompleted(ByVal sender As Base.Download, ByVal ea As HistQuotesDownloadCompletedEventArgs)

        Friend ReadOnly mFinanceHelper As New FinanceHelper
        Private mTextEncoding As System.Text.Encoding = System.Text.Encoding.UTF8

        ''' <summary>
        ''' The text encoding for downloading quotes NOT from YQL server.
        ''' </summary>
        ''' <value></value>
        ''' <returns></returns>
        ''' <remarks></remarks>
        Public Property TextEncoding() As System.Text.Encoding Implements Base.IStringDownload.TextEncoding
            Get
                Return mTextEncoding
            End Get
            Set(ByVal value As System.Text.Encoding)
                mTextEncoding = value
            End Set
        End Property

        ''' <summary>
        ''' Downloads historic quotes data.
        ''' </summary>
        ''' <param name="managedID">The managed ID</param>
        ''' <param name="fromDate">The startdate of the reviewed period</param>
        ''' <param name="todate">The enddate of the reviewed period</param>
        ''' <param name="interval">The trading period interval</param>
        ''' <returns></returns>
        ''' <remarks></remarks>
        Public Function Download(ByVal managedID As IID, ByVal fromDate As Date, ByVal todate As Date, ByVal interval As HistQuotesInterval) As HistQuotesResponse
            If managedID Is Nothing Then Throw New ArgumentNullException("managedID", "The passed ID is null.")
            Return Me.Download(managedID.ID, fromDate, todate, interval)
        End Function
        ''' <summary>
        ''' Downloads historic quotes data.
        ''' </summary>
        ''' <param name="unmanagedID">The unmanaged ID</param>
        ''' <param name="fromDate">The startdate of the reviewed period</param>
        ''' <param name="todate">The enddate of the reviewed period</param>
        ''' <param name="interval">The trading period interval</param>
        ''' <returns></returns>
        ''' <remarks></remarks>
        Public Function Download(ByVal unmanagedID As String, ByVal fromDate As Date, ByVal todate As Date, ByVal interval As HistQuotesInterval) As HistQuotesResponse
            If unmanagedID.Trim = String.Empty Then Throw New ArgumentNullException("unmanagedID", "The passed ID is empty.")
            Return Me.Download(New String() {unmanagedID}, fromDate, todate, interval)
        End Function
        ''' <summary>
        ''' Downloads historic quotes data.
        ''' </summary>
        ''' <param name="managedIDs">The managed ID</param>
        ''' <param name="fromDate">The startdate of the reviewed period</param>
        ''' <param name="todate">The enddate of the reviewed period</param>
        ''' <param name="interval">The trading period interval</param>
        ''' <returns></returns>
        ''' <remarks></remarks>
        Public Function Download(ByVal managedIDs As IEnumerable(Of IID), ByVal fromDate As Date, ByVal toDate As Date, ByVal interval As HistQuotesInterval) As HistQuotesResponse
            If managedIDs Is Nothing Then Throw New ArgumentNullException("managedIDs", "The passed list is null.")
            Return Me.Download(mFinanceHelper.IIDsToStrings(managedIDs), fromDate, todate, interval)
        End Function
        ''' <summary>
        ''' Downloads historic quotes data.
        ''' </summary>
        ''' <param name="unmanagedIDs">The unmanaged ID</param>
        ''' <param name="fromDate">The startdate of the reviewed period</param>
        ''' <param name="todate">The enddate of the reviewed period</param>
        ''' <param name="interval">The trading period interval</param>
        ''' <returns></returns>
        ''' <remarks></remarks>
        Public Function Download(ByVal unmanagedIDs As IEnumerable(Of String), ByVal fromDate As Date, ByVal toDate As Date, ByVal interval As HistQuotesInterval) As HistQuotesResponse
            If unmanagedIDs Is Nothing Then Throw New ArgumentNullException("unmanagedID", "The passed ID is empty.")
            Me.CheckDates(fromDate, todate)
            Dim url As String = Me.DownloadURL(unmanagedIDs, fromDate, todate, interval)
            Dim ids() As String = mFinanceHelper.CleanIDfromAT(unmanagedIDs)
            Return Me.ToResponse(MyBase.DownloadStream(url), ids, ids.Length > 1)
        End Function

        ''' <summary>
        ''' Starts an asynchronous download of historic quotes data.
        ''' </summary>
        ''' <param name="managedID">The managed ID</param>
        ''' <param name="fromDate">The startdate of the reviewed period</param>
        ''' <param name="todate">The enddate of the reviewed period</param>
        ''' <param name="interval">The trading period interval</param>
        ''' <param name="userArgs">Individual user argument</param>
        ''' <remarks></remarks>
        Public Overloads Sub DownloadAsync(ByVal managedID As IID, ByVal fromDate As Date, ByVal toDate As Date, ByVal interval As HistQuotesInterval, Optional ByVal userArgs As Object = Nothing)
            If managedID Is Nothing Then Throw New ArgumentNullException("managedID", "The passed ID is null.")
            Me.DownloadAsync(managedID.ID, fromDate, toDate, interval, userArgs)
        End Sub
        ''' <summary>
        ''' Starts an asynchronous download of historic quotes data.
        ''' </summary>
        ''' <param name="unmanagedID">The unmanaged ID</param>
        ''' <param name="fromDate">The startdate of the reviewed period</param>
        ''' <param name="todate">The enddate of the reviewed period</param>
        ''' <param name="interval">The trading period interval</param>
        ''' <param name="userArgs">Individual user argument</param>
        ''' <remarks></remarks>
        Public Overloads Sub DownloadAsync(ByVal unmanagedID As String, ByVal fromDate As Date, ByVal toDate As Date, ByVal interval As HistQuotesInterval, Optional ByVal userArgs As Object = Nothing)
            If unmanagedID = String.Empty Then Throw New ArgumentNullException("unmanagedID", "The passed ID is empty.")
            Me.DownloadAsync(New String() {unmanagedID}, fromDate, toDate, interval, userArgs)
        End Sub
        ''' <summary>
        ''' Starts an asynchronous download of historic quotes data.
        ''' </summary>
        ''' <param name="managedIDs">The managed list of IDs</param>
        ''' <param name="fromDate">The startdate of the reviewed period</param>
        ''' <param name="todate">The enddate of the reviewed period</param>
        ''' <param name="interval">The trading period interval</param>
        ''' <param name="userArgs">Individual user argument</param>
        ''' <remarks></remarks>
        Public Overloads Sub DownloadAsync(ByVal managedIDs As IEnumerable(Of IID), ByVal fromDate As Date, ByVal toDate As Date, ByVal interval As HistQuotesInterval, Optional ByVal userArgs As Object = Nothing)
            If managedIDs Is Nothing Then Throw New ArgumentNullException("managedIDs", "The passed list is null.")
            Me.DownloadAsync(mFinanceHelper.IIDsToStrings(managedIDs), fromDate, toDate, interval, userArgs)
        End Sub
        ''' <summary>
        ''' Starts an asynchronous download of historic quotes data.
        ''' </summary>
        ''' <param name="unmanagedIDs">The unmanaged list of IDs</param>
        ''' <param name="fromDate">The startdate of the reviewed period</param>
        ''' <param name="todate">The enddate of the reviewed period</param>
        ''' <param name="interval">The trading period interval</param>
        ''' <param name="userArgs">Individual user argument</param>
        ''' <remarks></remarks>
        Public Overloads Sub DownloadAsync(ByVal unmanagedIDs As IEnumerable(Of String), ByVal fromDate As Date, ByVal toDate As Date, ByVal interval As HistQuotesInterval, Optional ByVal userArgs As Object = Nothing)
            If unmanagedIDs Is Nothing Then Throw New ArgumentNullException("unmanagedIDs", "The passed list is null.")
            Me.CheckDates(fromDate, toDate)
            Dim args As New AsyncDownloadArgs(userArgs, mFinanceHelper.CleanIDfromAT(unmanagedIDs), fromDate, toDate, interval)
            MyBase.DownloadStreamAsync(Me.DownloadURL(args.IDs, fromDate, toDate, interval), args)
        End Sub

        ''' <summary>
        ''' Default constructor
        ''' </summary>
        ''' <remarks></remarks>
        Public Sub New()
        End Sub

        Private Sub DownloadAsync_Completed(ByVal sender As Base.Download, ByVal e As Base.StreamDownloadCompletedEventArgs) Handles MyBase.AsyncStreamDownloadCompleted
            If e IsNot Nothing AndAlso e.UserArgs IsNot Nothing AndAlso TypeOf e.UserArgs Is AsyncDownloadArgs Then
                Dim dlArgs As AsyncDownloadArgs = DirectCast(e.UserArgs, AsyncDownloadArgs)
                Dim args As New HistQuotesDownloadCompletedEventArgs(dlArgs.UserArgs, Me.ToResponse(e.Response, dlArgs.IDs, dlArgs.JSON), dlArgs.IDs, dlArgs.FromDate, dlArgs.ToDate, dlArgs.Interval)
                RaiseEvent AsyncDownloadCompleted(Me, args)
            End If
        End Sub

        Private Function DownloadURL(ByVal ids As IEnumerable(Of String), ByVal fromDate As Date, ByVal toDate As Date, ByVal interval As HistQuotesInterval) As String
            Dim idArr() As String = mHelper.EnumToArray(ids)
            If idArr.Length = 0 Then
                Throw New ArgumentNullException("unmanagedIDs", "The passed list is empty")
            Else
                If idArr.Length = 1 Then
                    If idArr(0).Trim = String.Empty Then
                        Throw New ArgumentNullException("id", "The passed ID is empty.")
                    Else
                        Dim url As New Text.StringBuilder
                        url.Append("http://ichart.yahoo.com/table.csv?s=")
                        url.Append(Uri.EscapeDataString(mHelper.CleanYqlParam(mFinanceHelper.CleanIndexID(idArr(0)).ToUpper)))
                        url.Append("&a=")
                        url.Append(fromDate.Month - 1)
                        url.Append("&b=")
                        url.Append(fromDate.Day)
                        url.Append("&c=")
                        url.Append(fromDate.Year)
                        url.Append("&d=")
                        url.Append(toDate.Month - 1)
                        url.Append("&e=")
                        url.Append(toDate.Day)
                        url.Append("&f=")
                        url.Append(toDate.Year)
                        url.Append("&g=")
                        url.Append(mFinanceHelper.GetHistQuotesInterval(interval))
                        url.Append("&ignore=.csv")
                        Return url.ToString
                    End If
                Else
                    Dim url As New Text.StringBuilder
                    url.Append("url in (")
                    For i As Integer = 0 To idArr.Length - 1
                        url.Append("'"c)
                        url.Append(Me.DownloadURL(New String() {mHelper.CleanYqlParam(mFinanceHelper.CleanIndexID(idArr(i)).ToUpper)}, fromDate, toDate, interval))
                        url.Append("'"c)
                        If i < idArr.Length - 1 Then url.Append(","c)
                    Next
                    url.Append(")")
                    Return mHelper.YqlUrl("*", "csv", url.ToString, Nothing, True)
                End If
            End If
        End Function

        Private Function ToResponse(ByVal resp As Base.StreamResponse, ByVal ids() As String, ByVal json As Boolean) As HistQuotesResponse
            Dim text As String = mHelper.StreamToString(resp.Result, mTextEncoding)
            Dim quotes(-1) As HistQuotesDataChain
            If resp.Connection.State = Base.ConnectionState.Success Then
                If json Then : quotes = Me.ConvertJSON(text, ids)
                Else : quotes = Me.ConvertCSV(text, ids(0))
                End If
            End If
            Return New HistQuotesResponse(resp.Connection, quotes)
        End Function

        Private Function ConvertJSON(ByVal text As String, ByVal ids() As String) As HistQuotesDataChain()
            Dim quotes As New List(Of HistQuotesDataChain)
            Dim chain As New HistQuotesDataChain
            Dim reg As New Regex("{""col0"":"".*?"",""col1"":"".*?"",""col2"":"".*?"",""col3"":"".*?"",""col4"":"".*?"",""col5"":"".*?"",""col6"":"".*?""}")
            Dim matches As MatchCollection = reg.Matches(text)
            If matches.Count > 0 Then

                For Each m As Match In matches
                    If m.Success Then
                        Dim columns() As String = m.Value.Replace("{", "").Replace("}", "").Split(New Char() {","c})
                        If columns.Length = 7 Then

                            If columns(0) = """col0"":""Date""" Then
                                If chain.Count > 0 Then
                                    Dim id As String = String.Empty
                                    If quotes.Count <= ids.Length - 1 Then id = ids(quotes.Count)
                                    chain.SetID(id)
                                    quotes.Add(chain)
                                    chain = New HistQuotesDataChain
                                End If
                            Else
                                Dim hq As New HistQuoteData
                                For Each col As String In columns
                                    Dim values() As String = col.Replace("""", "").Split(":"c)
                                    If values.Length = 2 Then
                                        Select Case values(0)
                                            Case "col0"
                                                Date.TryParse(values(1), mFinanceHelper.DefaultYqlCulture, Globalization.DateTimeStyles.AdjustToUniversal, hq.TradingDate)
                                            Case "col1"
                                                Double.TryParse(values(1), Globalization.NumberStyles.Any, mFinanceHelper.DefaultYqlCulture, hq.Open)
                                            Case "col2"
                                                Double.TryParse(values(1), Globalization.NumberStyles.Any, mFinanceHelper.DefaultYqlCulture, hq.High)
                                            Case "col3"
                                                Double.TryParse(values(1), Globalization.NumberStyles.Any, mFinanceHelper.DefaultYqlCulture, hq.Low)
                                            Case "col4"
                                                Double.TryParse(values(1), Globalization.NumberStyles.Any, mFinanceHelper.DefaultYqlCulture, hq.Close)
                                            Case "col5"
                                                Long.TryParse(values(1), Globalization.NumberStyles.Any, mFinanceHelper.DefaultYqlCulture, hq.Volume)
                                            Case "col6"
                                                Double.TryParse(values(1), Globalization.NumberStyles.Any, mFinanceHelper.DefaultYqlCulture, hq.CloseAdjusted)
                                        End Select
                                    End If
                                Next col
                                chain.Add(hq)
                            End If

                        End If
                    End If
                Next m

                If chain.Count > 0 Then
                    Dim id As String = String.Empty
                    If quotes.Count <= ids.Length - 1 Then id = ids(quotes.Count)
                    chain.SetID(id)
                    quotes.Add(chain)
                End If
                chain = Nothing
            End If
            Return quotes.ToArray
        End Function
        Private Function ConvertCSV(ByVal text As String, ByVal id As String) As HistQuotesDataChain()
            Dim lst As New List(Of HistQuoteData)
            If text <> String.Empty Then
                Dim ci As Globalization.CultureInfo = mFinanceHelper.DefaultYqlCulture

                Dim table()() As String = mHelper.CsvTextToStringTable(text, ","c)
                If table.Length > 1 Then
                    For i As Integer = 0 To table.Length - 1
                        If table(i).Length = 7 Then
                            Dim qd As New HistQuoteData
                            If Date.TryParse(table(i)(0), ci, Globalization.DateTimeStyles.None, qd.TradingDate) AndAlso _
                               Double.TryParse(table(i)(1), Globalization.NumberStyles.Currency, ci, qd.Open) AndAlso _
                               Double.TryParse(table(i)(2), Globalization.NumberStyles.Currency, ci, qd.High) AndAlso _
                               Double.TryParse(table(i)(3), Globalization.NumberStyles.Currency, ci, qd.Low) AndAlso _
                               Double.TryParse(table(i)(4), Globalization.NumberStyles.Currency, ci, qd.Close) AndAlso _
                               Long.TryParse(table(i)(5), Globalization.NumberStyles.Integer, ci, qd.Volume) AndAlso _
                               Double.TryParse(table(i)(6), Globalization.NumberStyles.Currency, ci, qd.CloseAdjusted) Then
                                lst.Add(qd)
                            End If
                        End If
                    Next
                End If
            End If
            Return New HistQuotesDataChain() {New HistQuotesDataChain(id, lst.ToArray)}
        End Function
        Private Sub CheckDates(ByVal fromDate As Date, ByVal toDate As Date)
            If fromDate > toDate Then Throw New ArgumentNullException("date", "The start date is later than the end date.")
        End Sub

        Private Class AsyncDownloadArgs
            Inherits Base.DownloadEventArgs
            Public IDs() As String
            Public FromDate As Date
            Public ToDate As Date
            Public Interval As HistQuotesInterval
            Public JSON As Boolean = False
            Public Sub New(ByVal userArgs As Object, ByVal i() As String, ByVal fDate As Date, ByVal tDate As Date, ByVal inter As HistQuotesInterval)
                MyBase.New(userArgs)
                Me.IDs = i
                Me.FromDate = fDate
                Me.ToDate = tDate
                Me.Interval = inter
                Me.JSON = Me.IDs.Length > 1
            End Sub
        End Class

    End Class

    ''' <summary>
    ''' Provides information and response of an asynchronous historic quote download.
    ''' </summary>
    ''' <remarks></remarks>
    Public Class HistQuotesDownloadCompletedEventArgs
        Inherits Base.DownloadCompletedEventArgs

        Private mIDs() As String
        Private mFromDate, mToDate As Date
        Private mInterval As HistQuotesInterval

        ''' <summary>
        ''' Gets the ID of the stock, index, exchange rate, etc.
        ''' </summary>
        ''' <value></value>
        ''' <returns></returns>
        ''' <remarks></remarks>
        Public ReadOnly Property IDs() As String()
            Get
                Return mIDs
            End Get
        End Property
        ''' <summary>
        ''' Gets the startdate of the reviewed period.
        ''' </summary>
        ''' <value></value>
        ''' <returns></returns>
        ''' <remarks></remarks>
        Public ReadOnly Property FromDate() As Date
            Get
                Return mFromDate
            End Get
        End Property
        ''' <summary>
        ''' Gets the enddate of the reviewed period.
        ''' </summary>
        ''' <value></value>
        ''' <returns></returns>
        ''' <remarks></remarks>
        Public ReadOnly Property ToDate() As Date
            Get
                Return mToDate
            End Get
        End Property
        ''' <summary>
        ''' Gets the trading period interval.
        ''' </summary>
        ''' <value></value>
        ''' <returns></returns>
        ''' <remarks></remarks>
        Public ReadOnly Property Interval() As HistQuotesInterval
            Get
                Return mInterval
            End Get
        End Property
        ''' <summary>
        ''' Gets the response with historic quote data.
        ''' </summary>
        ''' <value></value>
        ''' <returns></returns>
        ''' <remarks></remarks>
        Public Overloads ReadOnly Property Response() As HistQuotesResponse
            Get
                Return DirectCast(MyBase.Response, HistQuotesResponse)
            End Get
        End Property

        Friend Sub New(ByVal userArgs As Object, ByVal resp As HistQuotesResponse, ByVal id() As String, ByVal fDate As Date, ByVal tDate As Date, ByVal inter As HistQuotesInterval)
            MyBase.New(userArgs, resp)
            mIDs = id
            mFromDate = fDate
            mToDate = tDate
            mInterval = inter
        End Sub

    End Class

    ''' <summary>
    ''' Provides connection information and downloaded historic quote data.
    ''' </summary>
    ''' <remarks></remarks>
    Public Class HistQuotesResponse
        Inherits Base.Response

        ''' <summary>
        ''' Gets the received historic quote data.
        ''' </summary>
        ''' <value></value>
        ''' <returns></returns>
        ''' <remarks></remarks>
        Public Overloads ReadOnly Property Result() As HistQuotesDataChain()
            Get
                Return TryCast(MyBase.Result, HistQuotesDataChain())
            End Get
        End Property

        Friend Sub New(ByVal info As Base.ConnectionInfo, ByVal result() As HistQuotesDataChain)
            MyBase.New(info, result)
        End Sub

    End Class

End Namespace